<?php
/**
 * 分区数据访问对象
 * 
 * 通过这个对象你可以访问到一个模型的配置
 *
 * @package if
 * @subpackage module.activerecord
 * @since 1.0
 */
class IDbPartitionDAO extends IObject {
	private $props = array();
	
	/**
	 * 数据库驱动
	 *
	 * @var IDbDriver
	 */
	private $driver;
	
	private $table;
	
	private $quotedTable;
	
	private $type;
	
	private $columns = array();
	
	private $pkName;
	
	private $readonly = false;
	
	private $relations = array();
	
	private $validates = array();
	
	/**
	 * Enter description here...
	 *
	 * @var IDbPartitionQuery
	 */
	private static $querys = array();
	
	private $db;
	
	private $autofill = array();
	
	private $conds = array();
	
	private $relationSetuped = false;
	
	private $fillTime = true;
	
	private $composite = array();
	
	/**
	 * 构造DAO
	 *
	 * @param string $type 记录类型
	 * @since 1.0
	 */
	function __construct($type, $table) {
		$this->table=$table;
	
		//类文件是否存在
		if (!class_exists($type, true)) {
			throw new IActiveRecordException("record class '{$type}' does not exist", 0x100042);
		}
		if (!is_subclass_of($type, "IDbPartitionRecord")) {
			throw new IActiveRecordException("'{$type}' must be inherited from IDbPartitionRecord", 0x100043);
		}
		
		//类型
		$this->type = $type;
		
		//获取配置
		$this->props = o("@activerecord.records.{$this->type}");
		$props = get_class_vars($type);
		$this->props =  empty($this->props)?$props:array_merge($this->props, $props);
		
		//驱动
		if (isset($this->props["_driver"])) {
			$config = $this->props["_driver"];
			if (is_string($config) && strstr($config, ",")) {
				$config = explode(",", preg_replace("/\s+/", "", $config));
			}
			if (!is_array($config)) {
				$config = array($config);
			}
			$count = count($config);
			if ($count == 1) {
				$config = $config[0];
			}
			else {
				$config = $config[rand(0, $count - 1)];
			}
		}
		else {
			$config = "default";
		}
		$this->db = $config;
		$this->driver = IDb::driverByOpt($config);
		
		//表名
		$this->table = o("@activerecord.table.prefix") . $this->table . o("@activerecord.table.suffix");
		$this->quotedTable = $this->driver->quoteKeyword($this->table);
		
		
		//columns
		if (isset($this->props["_columns"])) {
			$columns = $this->props["_columns"];
			if (!is_array($columns)) {
				throw new IActiveRecordException("columns of '{$type}' must be an array", 0x100044);
			}
			else {
				$this->columns = $columns;
			}
		}
		else {
			$fields = $this->driver->describeTable($this->table);
			$this->columns = array_keys($fields);
			foreach ($fields as $field) {
				if ($field["primary_key"]) {
					$this->pkName = $field["name"];
					break;
				}
			}
		}
		
		//pk
		if (isset($this->props["_pk"])) {
			$this->pkName = $this->props["_pk"];
		}
		if (!$this->pkName) {
			throw new IActiveRecordException("'{$type}' must define a primary key (via \$_pk defination)", 0x100045);
		}
		
		//readonly
		if (isset($this->props["_readonly"])) {
			$this->readonly = $this->props["_readonly"];
		}
		
		//validators
		if (isset($this->props["_validate"]) && is_array($this->props["_validate"])) {
			$this->validates = $this->props["_validate"];
		}
		
		//_fill_create,_fill_read,_fill_update,_fill_delete
		foreach (array(	"_fill_create", 
						"_fill_read", 
						"_fill_update", 
						"_fill_delete"
					) as $v) {
			if (isset($this->props[$v]) && is_array($this->props[$v])) {
				list(, , $action) = explode("_", $v);
				$this->autofill[$action] = $this->props[$v];
			}
		}
		
		//_fill_time
		if (isset($this->props["_fill_time"])) {
			$this->fillTime = $this->props["_fill_time"];
		}
		
		//_conds
		if (isset($this->props["_conds"])) {
			$this->conds = is_array($this->props["_conds"]) ? $this->props["_conds"] : array($this->props["_conds"]);
		}
		
		//composite
		//在v1.0.3中加入
		if (isset($this->props["_composite"]) && is_array($this->props["_composite"])) {
			$this->composite = $this->props["_composite"];
			foreach ($this->composite as $key => $value) {
				if (is_int($key) && is_string($value)) {
					unset($this->composite[$key]);
					$this->composite[$value] = array_combine($this->columns(), $this->columns()); 
				}
				else if (is_string($key)) {
					if (is_array($value)) {
						foreach ($value as $_key => $_value) {
							if (is_int($_key)) {
								unset($value[$_key]);
								$value[$_value] = $_value;
							}
						}
						$this->composite[$key] = $value;
					}
				}
				else {
					unset($this->composite[$key]);
				}
			}
		}		
	}
	
	/**
	 * 取得DAO
	 *
	 * @param string $type 记录类型
	 * @return IDbPartitionDAO
	 * @since 1.0
	 */
	static function dao($type, $table) {
		return new IDbPartitionDAO($type, $table);
	}
	
	/**
	 * 删除一条记录
	 * 
	 * 返回影响的行数
	 *
	 * @param IDbPartitionRecord $record 记录
	 * @param boolean $deep 暂时无用
	 * @return integer
	 * @since 1.0
	 */
	function delete($record, $deep) {
		if ($this->readonly) {
			throw new IActiveRecordException("can't delete readonly record", 0x100046);
		}
		$count = $this->driver->exec("DELETE FROM " . $this->quotedTable . " WHERE " . $this->driver->quoteKeyword($this->pkName) . "=" . $this->driver->quote($record->pk()));
		return $count;
	}
	
	/**
	 * 保存一条记录
	 *
	 * @param IDbPartitionRecord $record 记录
	 * @param boolean $enforce 更新操作时，是否强制不检查脏数据
	 * @return IDbPartitionRecord
	 * @since 1.0
	 */
	function save($record, $enforce = false) {
		if (!$record->isSaved()) {
			//构造insert
			$columns = array();
			$values = array();
			foreach ($this->columns as $column) {
				$value = $record->attr($column);
				if (!is_null($value)) {
					$value = $this->driver->quote($value);
					$columns[] = $this->driver->quoteKeyword($column);
					$values[] = $value;
				}
			}
			if (empty($values)) {
				throw new IActiveRecordException("can't insert empty record, need to specify attr values", 0x100047);
				return $record;
			}
			$count = $this->driver->exec("INSERT INTO " . $this->quotedTable . " (" . implode(",", $columns) . ") VALUES (" . implode(",", $values) . ")");
			if ($count > 0) {
				$record->setAttr($this->pkName, $this->driver->lastInsertId());	
			}
			$record->setSaved(true);
			$record->setOriginal($record->attrs());
		}
		else {
			if ($this->readonly) {
				throw new IActiveRecordException("can't update readonly record", 0x100048);
			}
			
			//查找有更新的字段
			$ori = $record->original();
			$attrs = $record->attrs();
			$columns = array();
			foreach ($this->columns as $column) {
				if (!array_key_exists($column, $attrs) || is_null($attrs[$column])) {
					continue;
				}
				if ($enforce) {
					$columns[] = $this->driver->quoteKeyword($column) . "=" . $this->driver->quote($record->attr($column));
				}
				else {
					if (array_key_exists($column, $ori)) {
						if ($ori[$column] != $attrs[$column]) {
	 						$columns[] = $this->driver->quoteKeyword($column) . "=" . $this->driver->quote($record->attr($column));
						}
					}
					else {
						$columns[] = $this->driver->quoteKeyword($column) . "=" . $this->driver->quote($record->attr($column));
					}
				}
			}
			if (!empty($columns)) {
				//pk value
				$pkValue = $record->attr($this->pkName);
				
				$this->driver->exec("UPDATE " . $this->quotedTable . " SET " . implode(",", $columns) . " WHERE " . $this->driver->quoteKeyword($this->pkName) . "=" . $this->driver->quote($pkValue));
			}
		}
		
		return $record;
	}
	
	/**
	 * Enter description here...
	 *
	 * @return IDbDriver
	 */
	function driver() {
		return $this->driver;
	}
	
	/**
	 * 取得记录类型
	 *
	 * @return string
	 * @since 1.0
	 */
	function type() {
		return $this->type;
	}
	
	/**
	 * 取得记录对应的数据表
	 *
	 * @return string
	 * @since 1.0
	 */
	function table() {
		return $this->table;
	}
	
	/**
	 * 新建一个记录
	 *
	 * @param array $attrs
	 * @return IDbPartitionRecord
	 * @since 1.0
	 */
	function create(array $attrs) {
		$class = $this->type;
		$record = new $class($this->table, $attrs);
		return $record;
	}
	
	/**
	 * 判断当前对象是否为只读的
	 *
	 * @return boolean
	 * @since 1.0
	 */
	function readonly() {
		return $this->readonly;
	}
	
	/**
	 * 取得主键名
	 *
	 * @return string
	 * @since 1.0
	 */
	function pkName() {
		return $this->pkName;
	}
	
	/**
	 * 取得所有列名
	 *
	 * @return array
	 * @since 1.0
	 */
	function columns() {
		return $this->columns;
	}
	
	/**
	 * 生成一个新的查询对象
	 *
	 * @param string $record 记录类型
	 * @return IDbPartitionQuery
	 * @since 1.0
	 */
	static function query($record, $table) {
		return new IDbPartitionQuery($record, $table);
	}
	
	/**
	 * 取得所有关联关系
	 *
	 * @return array
	 * @since 1.0
	 */
	function relations() {
		$this->_setupRelations();
		return $this->relations;
	}
	
	/**
	 * 取得某个属性的关联
	 *
	 * @param string $attr 属性
	 * @return string|null
	 * @since 1.0
	 */
	function relation($attr) {
		$relations = $this->relations();
		if (isset($relations[$attr])) {
			return $relations[$attr];
		}
		return null;
	}
	
	/**
	 * 取得验证
	 *
	 * @return array
	 * @since 1.0
	 */
	function validates() {
		return $this->validates;
	}
	
	/**
	 * 设置驱动
	 *
	 * @param string $driver 驱动配置名
	 * @since 1.0
	 */
	function setDriver($driver) {
		$this->driver = IDb::driverByOpt($driver);
	}
	
	/**
	 * DB选项名
	 *
	 * @return string
	 * @since 1.0
	 */
	function dbOptName() {
		return $this->db;
	}
	
	/**
	 * 自动填充
	 *
	 * @param IDbPartitionRecord $record 模型
	 * @param string $action 动作，create,read,update,delete
	 * @return array
	 * @since 1.0
	 */
	function autofill(IDbPartitionRecord $record, $action) {
		//fill time
		if ($this->fillTime) {
			if (is_bool($this->fillTime)) {
				$this->fillTime = $this->_time();
			}
			if ($action == "create" && in_array("created_at", $this->columns)) {
				$record->setAttr("created_at", $this->_expr($this->fillTime, $record));
			}
			else if ($action == "update" && in_array("updated_at", $this->columns)) {
				$record->setAttr("updated_at", $this->_expr($this->fillTime, $record));
			}
		}
		
		//auto fill
		if(isset($this->autofill[$action])) {
			foreach ($this->autofill[$action] as $attr => $expr) {
				$record->setAttr($attr, $this->_expr($expr, $record));
			}
		}
	}
	
	/**
	 * 默认条件
	 *
	 * @return array
	 * @since 1.0
	 */
	function conds() {
		return $this->conds;
	}
	
	/**
	 * 设置数据表名称
	 *
	 * @param string $table 表名称
	 * @since 1.0
	 */
	function setTable($table) {
		$this->table = $table;
		$this->quotedTable = $this->driver()->quoteKeyword($table);
	}
	
	private function _setupRelations() {
		if ($this->relationSetuped) {
			return;
		}
		
		//hasOne
		if (isset($this->props["_hasOne"]) && is_array($this->props["_hasOne"])) {
			foreach ($this->props["_hasOne"] as $key => $value) {
				if (!if_is_valid_variable_name($key)) {
					throw new IActiveRecordException("'{$key}' in {$this->type} not a valid relation", 0x100049);
				}
				if (!is_array($value) && strstr($value, ".")) {
					list($target, $column) = explode(".", $value, 2);
					if (!if_is_valid_variable_name($target) || !if_is_valid_variable_name($column)) {
						throw new IActiveRecordException("'{$value}' in {$this->type} not a valid attr", 0x100050);
					}
					$this->relations[$key] = array(
						"type" => IDbPartitionRecord::HAS_ONE, 
						"attr" => $key, 
						"fk" =>$this->pkName, 
						"target" => $target, 
						"by" => $column, 
						"cond" => "", 
						"order" => ""
					);
				}
				else if (is_array($value) && isset($value["target"])) {
					if (!isset($value["by"])) {//v1.1.2
						$value["by"] = $this->table . "_id";
					}
					$fk = isset($value["fk"])?$value["fk"]:$this->pkName;
					$this->relations[$key] = array(
						"type" => IDbPartitionRecord::HAS_ONE, 
						"attr" => $key, 
						"fk" =>$fk, 
						"target" => $value["target"], 
						"by" => $value["by"], 
						"cond" => isset($value["cond"]) ? $value["cond"] : "",
						"order" => isset($value["order"]) ? $value["order"] : ""
					);
				}
				else {
					throw new IActiveRecordException("'{$key}' in {$this->type} not a valid relation(require target)", 0x100051);
				}
			}
		}
		
		//hasMany
		if (isset($this->props["_hasMany"]) && is_array($this->props["_hasMany"])) {
			foreach ($this->props["_hasMany"] as $key => $value) {
				if (!if_is_valid_variable_name($key)) {
					throw new IActiveRecordException("'{$key}' in {$this->type} not a valid attr", 0x100052);
				}
				if (!is_array($value) && strstr($value, ".")) {
					list($target, $column) = explode(".", $value, 2);
					if (!if_is_valid_variable_name($target) || !if_is_valid_variable_name($column)) {
						throw new IActiveRecordException("'{$value}' in {$this->type} not a valid attr", 0x100053);
					}
					$this->relations[$key] = array(
						"type" => IDbPartitionRecord::HAS_MANY, 
						"attr" => $key, 
						"fk" =>$this->pkName, 
						"target" => $target, 
						"by" => $column, 
						"cond" => "", 
						"order" => ""
					);
				}
				else if (is_array($value) && isset($value["target"])) {
					if (!isset($value["by"])) {//v1.1.2
						$value["by"] = $this->table . "_id";
					}
					$fk = isset($value["fk"])?$value["fk"]:$this->pkName;
					$this->relations[$key] = array(
						"type" => IDbPartitionRecord::HAS_MANY, 
						"attr" => $key, 
						"fk" =>$fk, 
						"target" => $value["target"], 
						"by" => $value["by"], 
						"cond" => isset($value["cond"]) ? $value["cond"] : "",
						"order" => isset($value["order"]) ? $value["order"] : ""
					);
				}
				else {
					throw new IActiveRecordException("'{$key}' in {$this->type} not a valid relation(require target)", 0x100054);
				}
			}
		}

		//belongsTo - many-to-one|one-to-one
		if (isset($this->props["_belongsTo"]) && is_array($this->props["_belongsTo"])) {
			foreach ($this->props["_belongsTo"] as $key => $value) {
				if (!if_is_valid_variable_name($key)) {
					throw new IActiveRecordException("'{$key}' in {$this->type} not a valid attr", 0x100055);
				}
				if (is_array($value) && isset($value["target"])) {
					if (!isset($value["fk"])) {//v1.1.2
						$value["fk"] = IDbPartitionDAO::dao($value["target"], $this->table)->table() . "_id";
					}
					$targetDAO = self::dao($value["target"]);
					$by = isset($value["by"])?$value["by"]:$targetDAO->pkName();
					$this->relations[$key] = array(
						"type" => IDbPartitionRecord::BELONGS_TO, 
						"attr" => $key, 
						"fk" =>$value["fk"], 
						"target" => $value["target"], 
						"by" => $by, 
						"cond" => isset($value["cond"]) ? $value["cond"] : "",
						"order" => isset($value["order"]) ? $value["order"] : "");
				}
				else {
					throw new IActiveRecordException("'{$key}' in {$this->type} not a valid relation(require target)", 0x100056);
				}
			}
		}
		
		//hasAndBelongsToMany
		if (isset($this->props["_habtm"])) {
			$this->props["_hasAndBelongsToMany"] = $this->props["_habtm"];
		}
		if (isset($this->props["_hasAndBelongsToMany"]) && is_array($this->props["_hasAndBelongsToMany"])) {
			foreach ($this->props["_hasAndBelongsToMany"] as $key => $value) {
				if (!if_is_valid_variable_name($key)) {
					throw new IActiveRecordException("'{$key}' in {$this->type} not a valid attr", 0x100057);
				}
				if (is_array($value) && isset($value["joinTable"]) && isset($value["target"])) {
					$fk = isset($value["fk"])?$value["fk"]:$this->pkName;
					$by = isset($value["by"])?$value["by"]:self::dao($value["target"])->pkName();
					$joinFk = isset($value["joinFk"])?$value["joinFk"]:$this->table . "_id";
					$joinBy = isset($value["joinBy"])?$value["joinBy"]:self::dao($value["target"])->table() . "_id";
					$this->relations[$key] = array(
						"type" => IDbPartitionRecord::HAS_AND_BELONGS_TO_MANY, 
						"attr" => $key, 
						"fk" => $fk, 
						"by" => $by, 
						"joinFk" => $joinFk, 
						"joinBy" => $joinBy, 
						"target" => $value["target"], 
						"joinTable" => $value["joinTable"],
						"cond" => isset($value["cond"]) ? $value["cond"] : "",
						"order" => isset($value["order"]) ? $value["order"] : ""
					);
				}
				else {
					throw new IActiveRecordException("'{$key}' in {$this->type} not a valid relation(require joinTable,target)", 0x100058);
				}
			}
		}
		
		$this->relationSetuped = true;
	}
	
	/**
	 * 添加关系
	 *
	 * @param array $relation 关系定义
	 * @return boolean
	 * @since 1.0
	 */
	function addRelation(array $relation) {
		extract($relation);
		if (!isset($type) || !in_array($type, array(IDbPartitionRecord::HAS_ONE, IDbPartitionRecord::HAS_MANY, IDbPartitionRecord::BELONGS_TO, IDbPartitionRecord::HAS_AND_BELONGS_TO_MANY))) {
			return false;
		}
		if (!isset($attr) || !if_is_valid_variable_name($attr)) {
			return false;
		}
		$this->relations[$attr] = $relation;
		return true;
	}
	
	/**
	 * 取得当前模型的组合配置
	 *
	 * @param string $attr 组合的新属性名称，如果为null，则将取出所有配置的的composite
	 * @return array
	 * @since 1.0.3
	 */
	function composite($attr = null) {
		return is_null($attr) ? $this->composite : (isset($this->composite[$attr]) ? $this->composite[$attr] : array());
	}
}

?>